/*
 * spi_flash.xc
 * Defines functions used to interface with an SPI flash memory chip.
 */

#include <xs1.h>
#include <platform.h>
#include <print.h>
#include "at25.h"
#include "spi_master.h"
#include "spi_flash.h"

#include <stdlib.h>


void wait_for_device(port spi_ss, spi_master_interface &spi_if);
void spi_out_addr(spi_master_interface &i, unsigned int addr);
void enable_write(port spi_ss, spi_master_interface &spi_if);
void unprotect_sector(port spi_ss, spi_master_interface &spi_if, unsigned int addr);
void erase_64k_block(port spi_ss, spi_master_interface &spi_if, unsigned int addr);
unsigned char read_status_register(port spi_ss, spi_master_interface &spi_if);




/*spi_master_interface spi_if = {
  XS1_CLKBLK_1,
  XS1_CLKBLK_2,
  PORT_SPI_MOSI,
  PORT_SPI_CLK,
  PORT_SPI_MISO}; */



//use sector 6 for now
//#define START_ADDR 0x060000

#define LENGTH 256



void clockDown()
{
  unsigned coreid = get_core_id();
  write_pswitch_reg(coreid, XS1_PSWITCH_PLL_CLK_DIVIDER_NUM, 0x80);
}

//int flashtest();
/*
int main()
{
  par {
  on stdcore[1]: flashtest();
  on stdcore[0]: clockDown();
  on stdcore[2]: clockDown();
  on stdcore[3]: clockDown();
  }
}

int flashtest()
{
	int memptr;
	timer tmr;
	unsigned t, marker;
	int ct;

	char data[LENGTH];

	flash_init();


	printstr("Reading status register: \0x");
	printhexln(read_status_register());

	printstrln(" NOT Writing to memory...");
**
	// Make up some pseudo-random data to store in ROM
	srand(145);
	for (ct = 0; ct < LENGTH; ct++) {
		data[ct] = rand();
		printhex(data[ct]);
	}

	printstrln("");

	tmr :> t;
	memptr = flash_write(data, 12);
	tmr :> marker;
**
	memptr = START_ADDR;

	printstrln("NOT Writing complete");

	printstr("That took ");
	printuintln(marker-t);

	printstr("Reading status register: \0x");
	printhexln(read_status_register());

	printstrln("Reading back from ROM: ");

	while (1) {
		//tmr :> t;
		flash_read(data, memptr, LENGTH);
		//tmr :> marker;

		for (ct = 0; ct < LENGTH; ct++) {
			printhex(data[ct]);
		}

		printstrln("");

		//printstr("That took ");
		//printuintln(marker-t);
		memptr +=LENGTH;
	}

	flash_shutdown();

	while(1);
	return 0;
}
*/

void flash_init(port spi_ss, spi_master_interface &spi_if)
{
	spi_init(spi_if, DEFAULT_SPI_CLOCK_DIV);
	spi_deselect(spi_ss);
/*
	unprotect_sector(START_ADDR);
	erase_64k_block(START_ADDR);
*/
	wait_for_device(spi_ss, spi_if);
}

void flash_clear(port spi_ss, spi_master_interface &spi_if, int start_addr[], int n, chanend os)
{
	int i;

	for (i = 0; i < n; i++) {
		unprotect_sector(spi_ss, spi_if, start_addr[n]);
		erase_64k_block(spi_ss, spi_if, start_addr[n]);
		wait_for_device_or_os(spi_ss, spi_if, os);
	}

	return;
}

void flash_shutdown(spi_master_interface &spi_if)
{
	spi_shutdown(spi_if);
}

int flash_write(port spi_ss, spi_master_interface &spi_if, unsigned char data[], int length, int &current_addr)
{
	unsigned int address = current_addr;
	enable_write(spi_ss, spi_if);
	spi_select(spi_ss);
	spi_out_byte(spi_if, byte_prog);

	spi_out_addr(spi_if, current_addr);

	spi_out_buffer(spi_if, data, length);

	current_addr += length;

	spi_deselect(spi_ss);

	wait_for_device(spi_ss, spi_if);

	return address;
}

void flash_read(port spi_ss, spi_master_interface &spi_if, unsigned char data[], unsigned int addr, int length)
{
	spi_select(spi_ss);
	spi_out_byte(spi_if, read_array);
	spi_out_addr(spi_if, addr);
	spi_out_byte(spi_if, 0x00);	// Send a don't care

	spi_in_buffer(spi_if, data, length);

	spi_deselect(spi_ss);

	return;
}


void spi_select(port spi_ss)
{
  spi_ss <: 0;
}

void spi_deselect(port spi_ss)
{
  spi_ss <: 1;
}

/*
 * wait_for_device()
 * Does not return until the device is not busy.
 */
void wait_for_device(port spi_ss, spi_master_interface &spi_if)
{
	spi_select(spi_ss);
	spi_out_byte(spi_if, read_status_reg);

	while (spi_in_byte(spi_if) & 0x01) {
		asm("nop");			// op nop nop
		asm("nop");
	}
	spi_deselect(spi_ss);
}

unsigned char read_status_register(port spi_ss, spi_master_interface &spi_if)
{
	unsigned char status;

	spi_select(spi_ss);
	spi_out_byte(spi_if, read_status_reg);

	status = spi_in_byte(spi_if);

	spi_deselect(spi_ss);

	return status;
}

void spi_out_addr(spi_master_interface &i, unsigned int addr)
{
  spi_out_byte(i, (addr >> 16) & 0xFF);
  spi_out_byte(i, (addr >> 8) & 0xFF);
  spi_out_byte(i, addr & 0xFF);
}

void enable_write(port spi_ss, spi_master_interface &spi_if)
{
	spi_select(spi_ss);
	spi_out_byte(spi_if, write_enable);
	spi_deselect(spi_ss);

	asm("nop");			// op nop nop
	asm("nop");
}

void unprotect_sector(port spi_ss, spi_master_interface &spi_if, unsigned int addr)
{
	enable_write(spi_ss, spi_if);
	spi_select(spi_ss);
	spi_out_byte(spi_if, unprotect_sect);

	spi_out_addr(spi_if, addr);

	spi_deselect(spi_ss);
	//asm("nop");
	//asm("nop");
}

void erase_64k_block(port spi_ss, spi_master_interface &spi_if, unsigned int addr)
{
	enable_write(spi_ss, spi_if);
	spi_select(spi_ss);

	spi_out_byte(spi_if, block_erase_64k);

	spi_out_addr(spi_if, addr);

	spi_deselect(spi_ss);

	wait_for_device(spi_ss, spi_if);
}
